package org.liurb.springboot.starter.web.client.conf;

import cn.hutool.json.JSONUtil;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.liurb.springboot.starter.common.utils.RequestHttpUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.client.*;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.StopWatch;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * rest模板配置类
 *
 * 可通过在配置文件中，配置"web.starter.rest-template.config.enabled=true"
 *
 * 自定义相关的配置"web.starter.rest-template.config.connection"，如超时配置：
 * web.starter.rest-template.config.connection：
 *      connect-timeout: 15
 *      read-timeout: 8
 *      write-timeout: 15
 *      max-idle-connections: 5
 *      keep-alive-duration: 300
 *
 * @Author Liurb
 * @Date 2022/11/28
 */
@Slf4j
@ConditionalOnClass({RestTemplate.class})
@ConditionalOnProperty(
        prefix = "web.starter.rest-template.config",
        value = {"enabled"},
        matchIfMissing = true)
@AutoConfiguration
public class RestTemplateConfig {

    @Value("${web.starter.rest-template.config.connection.connect-timeout:15000}")
    private Integer connectTimeout;
    @Value("${web.starter.rest-template.config.connection.read-timeout:8000}")
    private Integer readTimeout;
    @Value("${web.starter.rest-template.config.connection.write-timeout:15000}")
    private Integer writeTimeout;
    @Value("${web.starter.rest-template.config.connection.max-idle-connections}")
    private Integer maxIdleConnections;

    @Value("${web.starter.rest-template.config.connection.keep-alive-duration}")
    private Long keepAliveDuration;

    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate(httpRequestFactory());

        return this.restTemplateHandler(restTemplate);
    }

    /**
     * http请求工厂
     *
     * @return
     */
    public ClientHttpRequestFactory httpRequestFactory() {
        return new OkHttp3ClientHttpRequestFactory(okHttpConfigClient());
    }

    /**
     * okhttp请求配置
     *
     * @return
     */
    public OkHttpClient okHttpConfigClient() {

        TrustManager[] trustManagers = buildTrustManagers();

        return new OkHttpClient().newBuilder().connectionPool(pool())
                .connectTimeout(connectTimeout, TimeUnit.SECONDS)
                .readTimeout(readTimeout, TimeUnit.SECONDS)
                .writeTimeout(writeTimeout, TimeUnit.SECONDS)
                .sslSocketFactory(createSSLSocketFactory(trustManagers), (X509TrustManager) trustManagers[0])
                .hostnameVerifier((hostname, session) -> true)
                .build();
    }

    /**
     * 生成安全套接字工厂
     *
     * 用于https请求的证书跳过
     *
     * @return
     */
    private SSLSocketFactory createSSLSocketFactory(TrustManager[] trustAllCerts) {
        SSLSocketFactory ssfFactory = null;
        try {
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new SecureRandom());
            ssfFactory = sc.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ssfFactory;
    }

    private TrustManager[] buildTrustManagers() {
        return new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[]{};
                    }
                }
        };
    }

    /**
     * 连接池
     *
     * @return
     */
    private ConnectionPool pool() {
        return new ConnectionPool(maxIdleConnections, keepAliveDuration, TimeUnit.SECONDS);
    }

    /**
     * RestTemplate处理
     *
     * @param restTemplate
     * @return
     */
    private RestTemplate restTemplateHandler(RestTemplate restTemplate) {
        // 添加拦截器
        List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
        MyRequestInterceptor myRequestInterceptor = new MyRequestInterceptor();
        interceptors.add(myRequestInterceptor);
        restTemplate.setInterceptors(interceptors);

        // 中文乱码，主要是 StringHttpMessageConverter的默认编码为ISO导致的
        List<HttpMessageConverter<?>> list = restTemplate.getMessageConverters();
        for (HttpMessageConverter converter : list) {
            if (converter instanceof StringHttpMessageConverter) {
                ((StringHttpMessageConverter) converter).setDefaultCharset(StandardCharsets.UTF_8);
                break;
            }
        }

        return restTemplate;
    }

    /**
     * 自定义的拦截器
     *
     * 主要打印日志
     *
     */
    class MyRequestInterceptor implements ClientHttpRequestInterceptor {

        @Override
        public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] body, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            ClientHttpResponse response = clientHttpRequestExecution.execute(httpRequest, body);
            //这里要创建备份，要不然会包io提前关闭的异常错误
            ClientHttpResponse responseCopy = new BufferingClientHttpResponseWrapper(response);

            stopWatch.stop();
            StringBuilder resBody = new StringBuilder();
            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(responseCopy.getBody(),
                    Charset.forName("UTF-8")))) {
                String line = bufferedReader.readLine();
                while (line != null) {
                    resBody.append(line);
                    line = bufferedReader.readLine();
                }
            }
            //当然图片、文件一类的就可以省了，打出日志没啥用处，此处的业务逻辑随意撸了，比如header头信息类似于  Accept 、Accept-Encoding 、Accept-Language、Connection 等等
            String reqBody = "";
            if (httpRequest.getHeaders().getContentType() != null && "POST".equals(httpRequest.getMethod().name())) {
                String requestBody = new String(body, Charset.forName("UTF-8"));
                String contentType = httpRequest.getHeaders().getContentType().toString();

                Map<String, Object> params = RequestHttpUtil.getPostDataByContentTypeAndBody(contentType, requestBody);

                reqBody = JSONUtil.toJsonStr(params);

            }

            log.info(JSONUtil.toJsonStr(RestLog.builder().costTime(stopWatch.getLastTaskTimeMillis()).headers(httpRequest.getHeaders()).method(httpRequest.getMethod().name())
                    .reqUrl(httpRequest.getURI().toString()).reqBody(reqBody).resBody(resBody.toString()).resStatus(responseCopy.getStatusCode().value()).build()));

            return responseCopy;
        }
    }



    final class BufferingClientHttpResponseWrapper implements ClientHttpResponse {

        private final ClientHttpResponse response;

        private byte[] body;


        BufferingClientHttpResponseWrapper(ClientHttpResponse response) {
            this.response = response;
        }


        @Override
        public HttpStatusCode getStatusCode() throws IOException {
            return this.response.getStatusCode();
        }

        @Override
        public int getRawStatusCode() throws IOException {
            return this.response.getStatusCode().value();
        }

        @Override
        public String getStatusText() throws IOException {
            return this.response.getStatusText();
        }

        @Override
        public HttpHeaders getHeaders() {
            return this.response.getHeaders();
        }

        @Override
        public InputStream getBody() throws IOException {
            if (this.body == null) {
                this.body = StreamUtils.copyToByteArray(this.response.getBody());
            }
            return new ByteArrayInputStream(this.body);
        }

        @Override
        public void close() {
            this.response.close();
        }
    }

    @Data
    @Builder
    @SuppressWarnings("rawtypes")
    private static class RestLog {
        private String reqUrl;
        private String method;
        private HttpHeaders headers;
        private String reqBody;
        private String resBody;
        private long costTime;
        private int resStatus;
    }

}
